#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import os
import re

import pwd

from common.const import ExecuteResultEnum, CMDResult
from common.logger import Logger
from common.util.cmd_utils import cmd_format
from informix.comm.comm import open_grep, execute_cmd, check_log_backup_path, identify_env_user
from informix.comm.common_cmd import CommonCommand
from informix.comm.const import FileConstant, ErrorCode, ON_LINE_CLUSTER, InformixBaseCommand, ON_LINE_SINGLE, \
    JsonConstant, GbaseInfo, GbaseCommand
from informix.comm.const import InformixInfo, StatusOrVersion, InformixCustomParmConstant, InformixVersion, \
    REOURCE_ON_LINE_SINGLE_STATS, GbaseVersion
from informix.comm.informix_cmd import InformixCommand

log = Logger().get_logger("informix.log")


class InformixInfos:
    """
    各个service对内接口
    不要全部提供静态方法
    """
    db_user = identify_env_user() if identify_env_user() else InformixInfo.INFORMIX_USER

    def __init__(self, param):
        self.param = param
        self.is_cluster = ""
        self.agent_ip_list_curr = ''
        self.instance_name_curr, self.instance_name_bound = "", ""
        self.sqlhosts_file_curr, self.sqlhosts_file_bound = "", ""
        self.onconfig_file_curr, self.onconfig_file_bound = "", ""
        self.custom_dict_curr = {}
        self.config_file_list = []
        self.init_param()

    @staticmethod
    def _check_server(db_user=InformixInfo.INFORMIX_USER):
        cmd = f'su - {db_user} -c "onstat -l"'
        ret, out, err = execute_cmd(cmd)
        if ret != CMDResult.SUCCESS:
            if "command not found" in err:
                return False
        return True

    @staticmethod
    def check_host_uuid(host_uuid_param):
        log.info(f"Enter check_host_uuid method.")
        query_host_uuid = InformixInfos.get_host_uuid()
        return query_host_uuid == host_uuid_param

    @staticmethod
    def get_host_uuid():
        log.info("Enter get_host_uuid to obtain host uuid.")
        ret, res_cont = CommonCommand.cat_file(FileConstant.HOSTSN_FILE)
        if ret:
            return res_cont.strip()
        log.error("Failed to get host uuid.")
        return ""

    @staticmethod
    def check_instance_name_in_sqlhosts(sqlhosts_file, instance_name):
        log.info("Enter check_instance_name_in_sqlhosts method.")
        ret, res_cont = CommonCommand.cat_file(sqlhosts_file)
        if ret:
            return InformixInfos._check_contain_instance(res_cont, instance_name)
        log.error("Failed to cat sqlhosts.")
        return False

    @staticmethod
    def check_instance_name_in_onconfig(onconfig_file, instance_name):
        log.info("Enter check_instance_name_in_onconfig method.")
        if open_grep(instance_name, onconfig_file):
            return True
        return False

    @staticmethod
    def check_config_file_exist(file_list):
        log.info("Enter check_config_file_exist method.")
        err_code_list = [ErrorCode.ERROR_SQLHOSTS_FILE_NOT_EXIST.value, ErrorCode.ERROR_ONCONFIG_FILE_NOT_EXIST.value]
        index = 0
        result = ErrorCode.MY_SUCCESS_CODE.value
        for file in file_list:
            if os.path.exists(file):
                index += 1
                continue
            result = err_code_list[index]
            break
        return result

    @staticmethod
    def get_root_dbs_file_path(file_path):
        result = open_grep('ROOTPATH', file_path)
        if not result:
            return ''
        for line in result:
            info_list = line.strip().split()
            if len(info_list) != 2:
                continue
            if info_list[-1].find('$') == 0:
                path_info_list = info_list[-1].split('/', 1)
                if len(path_info_list) != 2:
                    return ''
                env_info, path_info = path_info_list[0].strip('$'), path_info_list[1]
                cmd = cmd_format(InformixBaseCommand.ECHO_TARGET_ENV, env_info)
                cmd = cmd_format(InformixBaseCommand.EXEC_WITH_INFORMIX, cmd)
                ret, out, err = execute_cmd(cmd)
                if ret != CMDResult.SUCCESS:
                    return ''
                out = out.strip()
                return os.path.join(out, path_info)
            # Gbase8s场景，当ROOTPATH为绝对路径情况，仅判断路径存在且权限合理
            elif info_list[-1].startswith('/'):
                root_path = info_list[-1]
                cmd = f"ls {root_path}"
                cmd = cmd_format(GbaseCommand.EXEC_WITH_GBASEDBT, cmd)
                ret, out, err = execute_cmd(cmd)
                if ret != CMDResult.SUCCESS:
                    return ''
                return root_path
        return ''

    @staticmethod
    def get_bound_node_instance_in_cluster(custom_dict: dict):
        log.info("Enter get_bound_node_instance_in_cluster method.")
        ret, res_cont = InformixCommand(custom_dict).get_bound_node_instance_name()
        log.info(f"Exec get_bound_node_instance_name result : {ret};{res_cont}")
        if ret:
            return InformixInfos._get_instance_name(res_cont)
        log.error("Failed to obtain bound node instance.")
        return ""

    @staticmethod
    def get_ip_in_sqlhosts(sqlhosts_file, instance_name):
        log.info("Enter get_ip_in_sqlhosts to obtain ip.")
        ret, res_cont = CommonCommand.cat_file(sqlhosts_file)
        if ret:
            return InformixInfos._get_ip(res_cont, instance_name)
        log.error("Failed to cat sqlhosts.")
        return ""

    @staticmethod
    def get_servernum_in_config(onconfig_path):
        log.info("Enter get_servernum_in_config method.")
        target_list = CommonCommand.open_file_grep(onconfig_path, InformixInfo.SERVERNUM)
        if target_list:
            return InformixInfos._get_servernum_in_list(target_list)
        log.error("Failed to cat onconfig.xxx for servernum info.")
        return ""

    @staticmethod
    def _check_user_exist(db_user=InformixInfo.INFORMIX_USER):
        log.info("Enter check_user_exist to check if user(informix or gbasedbt) exist.")
        try:
            pwd.getpwnam(db_user).pw_gid
        except Exception as err:
            log.error(f"Get user(informix) failed, err: {err}.")
            return False
        return True

    @staticmethod
    def _get_instance_name(res_cont: str):
        log.info("Enter _get_instance_name method.")
        rule = r"^(primary|standard|HDR Secondary)\s+(on|off)"
        res_list = res_cont.split("\n")
        for text in res_list:
            strip_str = text.strip()
            if re.match(rule, strip_str):
                index = re.match(rule, strip_str).span()[1]
                new_str = strip_str[index: len(strip_str)].strip()
                info_list = list(filter(lambda x: x, new_str.split(" ")))
                if len(info_list) < 4:
                    log.error("The format of bound node info is not valid.")
                    return ""
                return info_list[0]
        log.error("Failed to get the instance of bound node")
        return ""

    @staticmethod
    def _check_contain_instance(res_cont, instance_name):
        log.info("Enter _check_contain_instance method.")
        res_list = res_cont.split("\n")
        for text in res_list:
            if text.strip().startswith(instance_name):
                return True
        log.error("Failed to match the line at sqlhosts with the instance name provided by user.")
        return False

    @staticmethod
    def _get_ip(res_cont, instance_name):
        log.info("Enter _get_ip method.")
        info_list = list(
            map(lambda x: x.strip(),
                filter(lambda x: x.strip().startswith(instance_name), res_cont.split("\n"))))
        log.info(f"The ip list maybe line : {info_list}")
        ip_line = "" if len(info_list) == 0 else info_list[0]  # 多个满足时，取第一条信息
        if ip_line:
            info_list = list(filter(lambda x: x, ip_line.split(" ")))
            ip = "" if len(info_list) < 4 else info_list[2]
            return ip
        log.error("Failed to get ip in sqlhosts.")
        return ""

    @staticmethod
    def _get_servernum_in_list(target_list: list):
        log.info("Enter _get_servernum_in_list method.")
        info_list = list(
            map(lambda x: x.strip(),
                filter(lambda x: x.strip().startswith(InformixInfo.SERVERNUM), target_list)))
        servernum_line = "" if len(info_list) == 0 else info_list[0]  # 多个满足时，取第一条信息
        if servernum_line:
            info_list = list(filter(lambda x: x, servernum_line.split(" ")))
            server_num = "" if len(info_list) < 2 else info_list[1]
            return server_num
        log.error("Failed to get servernum in onconfig.xxx.")
        return ""

    @classmethod
    def get_informix_version_or_status(cls, custom_dict: dict, types: str):
        log.info("Enter get_informix_version_or_status to obtain %s.", types)
        ret, res_cont = InformixCommand(custom_dict).show_status_and_version()
        if ret:
            return cls.get_version(res_cont) if StatusOrVersion.VERSION == types \
                else cls.get_status(res_cont)
        log.error(f"Failed to exec the 'onstat -l' cmd to get informix")
        return ""

    @classmethod
    def get_status(cls, res_cont: str):
        log.info("Enter _get_status method.")
        res_list = res_cont.split("\n")
        if len(res_list) <= 1:
            log.error("Failed to obtain Informix instance status.")
            return ""
        if cls.db_user == InformixInfo.INFORMIX_USER:
            version_line = res_list[1]
            if version_line.startswith(InformixInfo.INFORMIX_VERSION_PREFIX) and version_line.count("--") == 3:
                version_list = version_line.strip().split("--")
                return version_list[1].strip()
            return ""
        elif cls.db_user == GbaseInfo.GBASE_USER:
            return res_list[0].split("--")[0].strip()
        return ""

    @classmethod
    def get_version(cls, res_cont: str):
        log.info("Enter get_version method.")
        res_list = res_cont.split("\n")
        if len(res_list) <= 1:
            log.error(f"Get Informix database version failed.")
            return ""
        if cls.db_user == InformixInfo.INFORMIX_USER:
            version_line = res_list[1]
            if version_line.startswith(InformixInfo.INFORMIX_VERSION_PREFIX) and version_line.count("--") == 3:
                version_list = version_line.replace(InformixInfo.INFORMIX_VERSION_PREFIX, "").strip().split("--")
                InformixVersion.informix_version = version_list[0].strip()
                return InformixVersion.informix_version
            return ""
        elif cls.db_user == GbaseInfo.GBASE_USER:
            # 兼容控制台返回命令最后一行是空值
            version_line = res_list[-1] if res_list[-1] else res_list[-2]
            GbaseVersion.gbase_vesion = version_line.split('_')[0]
            return GbaseVersion.gbase_vesion
        return ""

    @classmethod
    def is_informix_version_11(cls, custom_dict: dict):
        version = cls.get_informix_version_or_status(custom_dict, StatusOrVersion.VERSION)
        log.info("Current informix version: %s", version)
        return True if version.startswith("11") else False

    def init_param(self):
        self.is_cluster = self.param.check_if_cluster()
        self.instance_name_curr = self.param.get_single_instance_name()
        self.sqlhosts_file_curr = self.param.get_single_sqlhost()
        self.onconfig_file_curr = self.param.get_single_onconfig()
        self.agent_ip_list_curr = self.param.get_agent_ip_list_curr()
        self.custom_dict_curr = {
            InformixCustomParmConstant.INSTANCE_NAME: self.instance_name_curr,
            InformixCustomParmConstant.SQLHOSTS_PATH: self.sqlhosts_file_curr,
            InformixCustomParmConstant.CONFIG_PATH: self.onconfig_file_curr
        }
        self.config_file_list = [self.sqlhosts_file_curr, self.onconfig_file_curr]

    def check_app(self):
        log.info("Enter check_app method.")
        fun_list = [
            self._check_host_env,
            self._check_informix_instance,
            self._check_on_line
        ]
        for fun in fun_list:
            error_code = fun()
            if error_code != ExecuteResultEnum.SUCCESS:
                return error_code
        return ExecuteResultEnum.SUCCESS

    def check_cluster(self):
        log.info("Enter check cluster method.")
        fun_list = [
            self._check_user_exist,
            self._check_server
        ]
        for fun in fun_list:
            result = fun(self.db_user)
            if not result:
                return ErrorCode.DB_NOT_EXIST.value
        error_code = self._check_log_backup_path()
        if error_code != ExecuteResultEnum.SUCCESS:
            return error_code
        return ExecuteResultEnum.SUCCESS

    def check_instance_ip(self):
        """
        查询节点ip在agentIpList中
        :return:
        """
        log.info("Enter check_instance_ip method.")
        ip_bound = ''
        bound_node_instance_name = ''
        if self.is_cluster:
            # 查询绑定节点
            bound_node_instance_name = self.get_bound_node_instance_in_cluster(self.custom_dict_curr)
            ip_bound = self.get_ip_in_sqlhosts(self.sqlhosts_file_curr, bound_node_instance_name)
            if not ip_bound:
                log.error(f"The ip of bound node is invalid.")
                return '', ''
        return bound_node_instance_name, ip_bound

    def get_support_info(self):
        """
        1. 查询SERVERNUM
        2. 查询informix版本信息
        :return:
        """
        database_version = InformixVersion.informix_version
        if self.db_user == GbaseInfo.GBASE_USER:
            database_version = GbaseVersion.gbase_vesion
        log.info("Enter get_support_info to support the information of SERVERNUM and Version.")
        server_num = self.get_servernum_in_config(self.onconfig_file_curr)
        if not server_num:
            log.error("Failed to obtain the server num at %s.", server_num)
            return "", database_version
        return server_num, database_version

    def _check_informix_instance(self):
        """
        检查文件中实例名与参数中的实例名是否匹配，即可以查到相关实例名所在行
        :return:
        """
        log.info("Enter check_informix_instance to check informix instance.")
        if not self.check_instance_name_in_sqlhosts(self.sqlhosts_file_curr, self.instance_name_curr):
            log.error("Failed to check instance at sqlhosts.")
            return ErrorCode.ERROR_INSTANCE_NOT_EXIST
        if not self.check_instance_name_in_onconfig(self.onconfig_file_curr, self.instance_name_curr):
            log.error("Failed to check instance at sqlhosts.")
            return ErrorCode.ERROR_INSTANCE_NOT_EXIST
        return ErrorCode.MY_SUCCESS_CODE

    def _check_on_line(self):
        """
        当前实例的状态是否在线的,注意区分集群或者单机
        :return:
        """
        log.info("Enter check_on_line to check the status of node.")
        get_status_ret = self.get_informix_version_or_status(self.custom_dict_curr, StatusOrVersion.STATUS)
        if self.is_cluster:
            if get_status_ret in ON_LINE_CLUSTER:
                return ErrorCode.MY_SUCCESS_CODE
            if get_status_ret not in ON_LINE_CLUSTER and get_status_ret in REOURCE_ON_LINE_SINGLE_STATS:
                return ErrorCode.ERROR_SINGLE_NODE_NOT_ALLOW_REGISTER_CLUSTER_INS
            else:
                return ErrorCode.DB_NOT_RUNNING.value
        else:
            if get_status_ret in ON_LINE_SINGLE:
                return ErrorCode.MY_SUCCESS_CODE
            if get_status_ret not in ON_LINE_SINGLE and get_status_ret in ON_LINE_CLUSTER:
                return ErrorCode.ERROR_CLUSTER_NODE_NOT_ALLOW_REGISTER_SINGLE_INS
            else:
                return ErrorCode.DB_NOT_RUNNING.value

    def _check_host_env(self):
        """
        1. 用户存在
        2. 配置文件存在
        3. 数据库安装
        :return: ErrorCode
        """
        log.info("Enter check_host_env to check host env.")
        if not self._check_user_exist(self.db_user):
            log.error("Failed to check user(informix or gbasedbt).")
            return ErrorCode.MY_ERROR_CODE
        code = self.check_config_file_exist(self.config_file_list)
        if code != ErrorCode.MY_SUCCESS_CODE.value:
            log.error("Config file does not exist.")
            return code
        return ErrorCode.MY_SUCCESS_CODE

    def _check_log_backup_path(self):
        """
        检查日志备份路径是否符合要求
        :return:
        """
        log.info("Enter check_log_backup_path")
        log_backup_path = self.param.appenv.get(JsonConstant.EXTEND_INFO, {}).get(
            JsonConstant.LOG_BACKUP_PATH, '')
        user = self.db_user
        if log_backup_path:
            return check_log_backup_path(user, log_backup_path)
        return ErrorCode.MY_SUCCESS_CODE
